var $;
var JSON;

function asyncHelper() {
  Game.initAsync.call(Game, arguments);
}

var Game = {
  gameId: 'org.ribizli.basicgameserver.AmoebaGame',
  TILE_SIZE : 20,
  playerMark : 'x',
  opponentMark : 'o',
  onlineRequestInterval: 10000,
  $f : null,
  $opponent : null,
  init : function(params) {
    this.$container = $("#" + params.field);

    this.$f = $('<div class="field" />');
    this.$lastMove = $('<div class="lastMove"/>').appendTo(this.$f);

    this.$popup = $('<div class="popup" />');
    this.$popup.updateBg = function() {
      $('.background', this).height(this.height());
    };
    this.toLoad = 2; // number of asynchronous loads
    this.$loginForm = $('<div class="loginForm" />').load(
        'elements/loginForm.html', null, asyncHelper);
    this.$scoreBoard = $('<div class="scoreBoard" />').load(
        'elements/scoreBoard.html', null, asyncHelper);

    var $image = $('<img src="elements/pics/progress.gif"/>');
    var $text = $('<div>Loading, please wait...</div>');
    this.$progress = $('<div class="progress" />').append($image).append($text);
    this.$progress.setProgressText = function(text) {
      $('div', this).text(text);
    };
    
    this.$error = $('<div class="error" />');
    this.$message = $('<div class="message" style="display:none;" />');
    
    this.$popup.append('<div class="background" />')
      .append(this.$progress)
      .append(this.$loginForm)
      .append(this.$message)
      .append(this.$error);
    this.$popup.updateBg();
    
    var $logger = $('<div class="logger" />');
    Log.setContainer($logger);
    
    var $menubar = $('<div class="menubar" />');
    this.$online = $('<span>-/-</span>');
    this.$logout = $('<a href="javascript:void(0);">logout</a>').click(function(){
      Game.setEnabled(false);
      Game.$message.hide();
      Game.$progress.show();
      Game.$popup.show();
      Game.$progress.setProgressText('Logging out...');
      Game.$popup.updateBg();
      Communication.stopPolling();
      Communication.logout();
    });
    $menubar
      .append($('<div class="logout"/>').append(this.$logout))
      .append($('<div style="float:right"> _</div>').click(function(){
        $logger.toggle();
        Log.scrollDown();
      }))
      .append($('<div class="online"/>')
          .append('players online: ')
          .append(this.$online)
    );
    
    this.$chat = $('<div class="chat"><div class="msg"/><input type="text" class="inp"/></div>');
    this.$chat.caption = 'Type message here (send w/ ENTER)';
    this.$chat.captionColor = '#666';
    this.$chat.textColor = 'black';
    this.$chat.$input = $('.inp', this.$chat);
    this.$chat.$input
      .attr('disabled', true)
      .val(this.$chat.caption)
      .css('color', this.$chat.captionColor)
      .focus(function(){
        $(this).css('color', Game.$chat.textColor);
        if (this.value == Game.$chat.caption) {
          this.value = '';
        }
      }).blur(function() {
        $(this).css('color', Game.$chat.captionColor);
        if (this.value == '') {
          this.value = Game.$chat.caption;
        }
      }).keypress(function(e) {
        if (e.which == 13 /*ENTER*/ && this.value != '') {
          Game.$chat.addMessage($('#sb_username').text(), this.value);
          Game.sendMessage(this.value);
          this.value = '';
        }
      });
    this.$chat.addMessage = function(sender, text) {
      var $msg = $(".msg", this);
      $msg.append('<strong>' + sender + '</strong>: ' + text + '<br/>');
      $msg.scrollTop($msg.attr('scrollHeight'));
    };

    this.$container.addClass('container').append(this.$popup)
      .append($menubar)
      .append(this.$f)
      .append(this.$scoreBoard)
      .append(this.$chat)
      .append($logger);
  },
  initAsync : function() {
    if (--this.toLoad > 0)
      return;

    this.$loginBtn = $('a.login', this.$loginForm);
    this.$loginBtn.click( function() {
      var $retype = $('tr.retype:visible', Game.$loginForm);
      if ($retype.length > 0) {
        $retype.hide();
        Game.setError('');
        Game.$popup.updateBg();
        return;
      }
      var username = $("#lg_username").val();
      var password = $("#lg_password").val();
      if (!username || !password) {
        Game.setError('Username/password not set!');
        return;
      }
      Game.setError('');
      Game.$loginForm.hide();
      Game.$progress.show();
      Game.$progress.setProgressText('Logging in, please wait...');
      Game.$popup.updateBg();
      $("#lg_password").val('');
      Communication.login(Game.gameId, username, password);
    });
    
    $("#lg_username").keypress(function(e) {
      if (e.which == 13 /*ENTER*/) {
        Game.$loginBtn.click();
      }
    });
    $("#lg_password").keypress(function(e) {
      if (e.which == 13 /*ENTER*/) {
        Game.$loginBtn.click();
      }
    });
    
    this.$registerBtn = $('a.register', this.$loginForm);
    this.$registerBtn.click( function() {
      var $retype = $('tr.retype:hidden', Game.$loginForm);
      if ($retype.length) {
        /* if not visible, show the retype row */
        $retype.show();
        Game.setError('');
        Game.$popup.updateBg();
      } else {
        /* if visible check the input and send registration request */
        var username = $("#lg_username").val();
        var password = $("#lg_password").val();
        var password2 = $("#lg_password2").val();
        if (!username || !password || !password2) {
          Game.setError('Please fill all fields!');
          return;
        }
        if (password != password2) {
          Game.setError('Password mismatch');
          $("#lg_password").val('');
          $("#lg_password2").val('');
          return;
        }
        Game.setError('');
        Communication.register(Game.gameId, username, password);
        $('tr.retype', Game.$loginForm).hide();
        Game.$loginForm.hide();
        Game.$progress.show();
        Game.$progress.setProgressText('Registering, please wait...');
        Game.$popup.updateBg();
      }
    });
    this.$loginForm.show();
    this.$popup.updateBg();
  },
  setEnabled : function(enabled) {
    if (enabled) {
      this.$f.click(this.clicked);
    } else {
      this.$f.unbind('click');
    }
  },
  clicked : function(ev) {
    var left = Game.$f.attr('offsetLeft') + Game.$container.attr('offsetLeft');
    var top = Game.$f.attr('offsetTop') + Game.$container.attr('offsetTop');
    var x = Math.floor((ev.offsetX ? ev.offsetX : ev.pageX
        - left) / Game.TILE_SIZE);
    var y = Math.floor((ev.offsetY ? ev.offsetY : ev.pageY
        - top) / Game.TILE_SIZE);
    Game.putMark(x, y, Game.playerMark, true);
  },
  putMark : function(x, y, mark, send) {
    if ($("#mark-" + x + "_" + y).length > 0 || mark != 'x' && mark != 'o') {
      return;
    }
    this.$lastMove.css({top:y * this.TILE_SIZE, left:x * this.TILE_SIZE});
    var $m = $('<div id="mark-' + x + '_' + y + '" class="fieldMark"></div>');
    this.$f.append($m);
    $m.css({
      'left': x * this.TILE_SIZE, 
      'top': y * this.TILE_SIZE,
      'background-image': 'url("elements/pics/' + mark + '_bg_transp.png")'
    });
    if (send) {
      this.yourTurn(false);
      var message = "{action:'MAKE_MOVE',x:" + x + ",y:" + y + "}";
      Communication.sendMessage(message);
      Log.l("Move sent: " + message);
    }
  },
  clear : function() {
    Log.l("Clear the field");
    $(".fieldMark").remove();
  },
  processMessage : function(message) {
    Log.l(message.action);
    if (message.action == "WAITING_FOR_OPPONENT") {
      this.$progress.setProgressText('Waiting for opponent...');
    } else if (message.action == "NEW_GAME") {
      this.$chat.$input.attr('disabled', false);
      $('#sb_oppname').text(message.opponent);
      $('#sb_oppScore').text(message.score);
      this.clear();
      this.yourTurn(message.first);
      this.$progress.hide();
      this.$popup.hide();
    } else if (message.action == "YOUR_TURN") {
      this.putMark(message.x, message.y, this.opponentMark, false);
      this.yourTurn(true);
    } else if (message.action == "YOU_WON") {
      this.endGame('You won!');
      /* winner row */
      /* message.winner_row; */
      if (message.score) {
        $('#sb_userScore').text(message.score[0]);
        $('#sb_oppScore').text(message.score[1]);
      }
    } else if (message.action == "YOU_LOOSE") {
      this.putMark(message.x, message.y, this.opponentMark, false);
      this.endGame('You lost!');
      if (message.score) {
        $('#sb_userScore').text(message.score[0]);
        $('#sb_oppScore').text(message.score[1]);
      }
    } else if (message.action == "OPPONENT_LEFT") {
      Communication.stopPolling();
      this.setEnabled(false);
      this.clearScores();
      this.$chat.$input.attr('disabled', true);
      this.welcome();
      this.$chat.addMessage('*game*', "Your opponent has left the field.");
    } else if (message.action == "USER_STATISTICS") {
      var online = message.online_users;
      var playing = message.playing_users;
      this.$online.text(playing + '/' + online);
      this.onlineQuery = setTimeout(function() { Game.requestUserStatistics(); return 0; }, this.onlineRequestInterval);
    } else if (message.action == "SEND_MESSAGE") {
      this.$chat.addMessage(message.opponent, message.message_text);
    } else if (message.action == "INVALID_MOVE") {
      Log.l('Invalid Move', true);
      this.$chat.addMessage('*game*', 'Invalid move, put again');
      this.yourTurn(true);
    } else if (message.action == "INVALID_STATE") {
      Log.l(message.action, true);
    } else if (message.action == "REMATCH") {
      this.$chat.addMessage('*game*', 'Your opponent has requested a rematch.');
    }
  },
  yourTurn: function(t) {
    this.setEnabled(t);
    if (t) {
      $('#sb_user').addClass('nextMove');
      $('#sb_opp').removeClass('nextMove');
    } else {
      $('#sb_user').removeClass('nextMove');
      $('#sb_opp').addClass('nextMove');
    }
  },
  endGame: function(message){
    this.$message.load('elements/endgame.html', null, function(){
      $('h1.title', Game.$message).text(message);
      Game.$progress.hide();
      Game.$message.show();
      Game.$popup.show();
      Game.$popup.updateBg();
      $('a.rematch', Game.$message).click(function(){
        Game.$progress.show().setProgressText('You have requested a rematch...');
        Game.$message.hide();
        Game.$popup.updateBg();
        Game.rematch();
      });
      $('a.newgame', Game.$message).click(function(){
        Game.$progress.show().setProgressText('Starting a new game...');
        Game.$message.hide();
        Game.$popup.updateBg();
        Game.newGame();
      });
    });
  },
  welcome: function(){
    this.$message.load(
      'elements/welcome.html', null, function() {
        Game.$progress.hide();
        Game.$message.show();
        Game.$popup.show();
        Game.$popup.updateBg();
        $('a.newgame', Game.$message).click(function() {
          Game.$progress.show().setProgressText('Starting a new game...');
          Game.$message.hide();
          Game.$popup.updateBg();
          Game.newGame();
        });
      });
  },
  loggedIn : function(details) {
    this.welcome();
    this.requestUserStatistics();
    
    this.$scoreBoard.css('visibility', 'visible');
    /* this.newGame(false); */
    this.$logout.css('visibility', 'visible');
    $('#sb_username').text(details.username);
    $('#sb_userScore').text(details.score);
  },
  loggedOut : function() {
    this.setEnabled(false);
    this.$logout.css('visibility', 'hidden');
    this.$progress.hide();
    this.$loginForm.show();
    this.$popup.updateBg();
    if (this.onlineQuery) { clearTimeout(this.onlineQuery); }
    this.$online.text('-/-');
    this.clearScores(true);
    this.$chat.$input.attr('disabled', true);
  },
  clearScores : function(user) {
    if (user) {
      $('#sb_username').text('');
      $('#sb_userScore').text('');
      this.$scoreBoard.css('visibility', 'hidden');
    }
    $('#sb_oppname').text('');
    $('#sb_oppScore').text('');
  },
  newGame : function() {
    Communication.setPolling(true);
    Communication.sendMessage("{action:'NEW_GAME'}");
  },
  rematch : function() {
    Communication.sendMessage("{action:'REMATCH'}");
  },
  registered : function() {
    Log.l('User registered');
    $('tr.retype:visible', this.$loginForm).hide();
    this.$popup.updateBg();
    this.$loginBtn.click();
  },
  updated : function() {
    /* TODO: change password */
    Log.l('User updated');
  },
  requestUserStatistics : function() {
    Communication.sendMessage("{action:'USER_STATISTICS'}");
  },
  setError : function(error) {
    this.$error.text(error);
  },
  sendMessage : function(text) {
    text.replace('\'', '\\\'');
    Communication.sendMessage("{action:'SEND_MESSAGE',message_text:'"+text+"'}");
  }
};

var Communication = {
  serviceURL : null,
  polling: false,
  pollInterval : null,
  token : null,
  messagePending : false,
  messageQueue : [],
  timer : null,
  init : function(opt) {
    this.serviceURL = opt.serviceURL;
    this.pollInterval = opt.pollInterval || 1000;
  },
  login : function(gameID, username, password) {
    if (this.token != null) {
      return;
    }
    this.sendMessage( {
      task : 'login',
      username : username,
      password : password,
      game_id : gameID
    });
  },
  logout : function() {
    if (this.token) {
      if (this.messagePending) {
        this.logoutRequested = true;
      } else {
        this.sendMessage( {
          task : 'logout',
          token : this.token
        });
      }
    }
  },
  register : function(gameID, username, password) {
    if (this.token) {
      Log.l('You must be logged out!', true);
      return;
    }
    this.sendMessage( {
      task : 'register',
      username : username,
      password : password,
      game_id : Game.gameId,
      details : '{"payload":"web"}'
    });
  },
  update : function(password) {
    if (!this.token) {
      Log.l('Must be logged in for this action!', true);
      return;
    }
    var o = {
      task : 'update',
      token : this.token,
      datails : '{"payload":"web-update"}'
    };
    if (password) {
      o.password = password;
    }
    this.sendMessage(o);
  },
  sendMessage : function(params) {
    if (typeof(params) == "string" && params != '') {
      this.messageQueue.push(params);
    }
    if (this.messagePending) {
      return;
    }
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
    if (typeof(params) != "object" || params == null) {
      params = {
        messages : '{messages:['+ this.messageQueue.join(',') +']}',
        task : 'message'
      };
      this.messageQueue = [];
      if (this.token) {
        params.token = this.token;
      }
    }
    $.ajax( {
      error : function(xhr, textStatus, errorThrown) {
        Log.l(textStatus + ' - ' + xhr.status + ', ' + xhr.readyState);
        Communication.requestReturned();
      },
      success : function(data, textStatus) {
        Communication.requestReturned();
        Communication.receiveMessage(data);
      },
      type : 'POST',
      url : this.serviceURL,
      dataType : 'json',
      data : params
    });
    this.messagePending = true;
  },
  requestReturned: function() {
    this.messagePending = false;
    if (this.polling) {
      this.timer = setTimeout(function() {
        Communication.timer = null;
        Communication.sendMessage(null);
        return 0;
      }, this.pollInterval);
    }
  },
  receiveMessage : function(data) {
    if (this.logoutRequested) {
      this.logoutRequested = false;
      this.stopPolling();
      this.logout();
      return;
    }
    if (data.token) {
      this.token = data.token;
      Log.l("logged in: " + this.token);
      Game.loggedIn(data.details);
    } else if (data.status) {
      if (data.task == 'logout') {
        /* user logged out */
        this.token = null;
        Log.l("logged out");
        Game.loggedOut();
      } else if (data.task == 'register') {
        /* user successfully registered */
        Game.registered();
      } else if (data.task == 'update') {
        /* user successfully changed his registration */
        Game.updated();
      }
    } else if (data.messages) {
      for (m in data.messages) {
        Game.processMessage(data.messages[m]);
      }
    } else if (data.error) {
      if (data.error == 'USER_ALREADY_REGISTERED') {
        Game.$progress.hide();
        Game.setError('This username is already registered!');
        Game.$loginForm.show();
        $('tr.retype:hidden', Game.$loginForm).show();
        Game.$popup.updateBg();
      } else if (data.error == 'USER_NOT_REGISTERED') {
        Game.$progress.hide();
        Game.setError('This username is not registered!');
        Game.$loginForm.show();
        Game.$popup.updateBg();
      } else if (data.error == 'USER_NOT_LOGGED') {
        alert(data.error);
      } else if (data.error == 'WRONG_PASSWORD') {
        Game.$progress.hide();
        Game.setError('Login failed, wrong password!');
        Game.$loginForm.show();
        Game.$popup.updateBg();
      } else if (data.error == 'INVALID_TOKEN') {
        /* Login session timed out, reset everything */
        Game.setError('Your session has timed out!');
        this.stopPolling();
        this.token = null;
        Game.loggedOut();
      }
      Log.l(data.error, true);
    }
  },
  setPolling: function(p) {
    this.polling = p;
    Log.l('Polling: ' + p);
  },
  stopPolling : function() {
    this.setPolling(false);
    if (this.timer) {
      if (this.messageQueue.length > 0) Log.l('stopPolling: Message queue is not empty!');
      clearInterval(this.timer);
      this.timer = null;
    }
  }
};

var Log = {
  $container: null,
  setContainer: function(jq) {
    this.$container = jq;
  },
  l : function(message, error) {
    if (this.$container == null) { return; }
    if (error) {
      message = '<span style="color:red;font-weight:bold;">' + message + '</span>';
    }
    this.$container.append(message + "<br/>");
    this.scrollDown();
  },
  scrollDown: function() {
    this.$container.scrollTop(this.$container.attr('scrollHeight'));
  }
};

$(document).ready( function() {
  /* org.ribizli.basicgameserver.AmoebaGame */

  Game.init( {
    field : "field"
  });

  Communication.init( {
    serviceURL : 'gameserver/'
  });

});
